const fs = require("fs");
const path = require("path");
const https = require("https");
const http = require("http");
const WebSocket = require("ws");
const client = require("prom-client");
const express = require("express");
const cors = require("cors");
const config = require('./config/index.js');

// 引入用户管理模块
const { GlobalUserManager, USER_ROLES, USER_STATUS, VOICE_STATUS } = require("./userManager");

// 引入房间管理模块
const { GlobalRoomManager, ROOM_TYPES, ROOM_STATUS, JOIN_POLICIES } = require("./roomManager");

// Create a Registry which registers the metrics
const register = new client.Registry();
// Add a default label which is added to all metrics
register.setDefaultLabels({
  app: "clocktower-online"
});

const PING_INTERVAL = 30000; // 30 seconds

let server;
let wss;
let app;

// 初始化Express应用
app = express();
app.use(cors({
  origin: config.CORS_ORIGINS,
  credentials: true
}));
app.use(express.json());

// 添加剧本API路由
app.use('/api/scripts', require('./routes/scripts'));
// 添加认证API路由
app.use('/api/auth', require('./routes/auth').router);
// 添加系统API路由
app.use('/api/system', require('./routes/system'));
// 添加角色管理API路由
app.use('/api/roles', require('./routes/roles'));

// 静态文件服务
app.use('/api/images', express.static(path.join(__dirname, '../src/data/scripts/images')));

if (process.env.NODE_ENV === "production") {
  // Production: HTTPS with SSL certificates
  const options = {};
  try {
    options.cert = fs.readFileSync("cert.pem");
    options.key = fs.readFileSync("key.pem");
    server = https.createServer(options);
    wss = new WebSocket.Server({
      server,
      verifyClient: info =>
        info.origin &&
        !!info.origin.match(
          /^https?:\/\/([^.]+\.github\.io|localhost|clocktower\.online|eddbra1nprivatetownsquare\.xyz)/i
        )
    });
    
    // 将Express应用挂载到HTTPS服务器
    server.on('request', app);
  } catch (error) {
    console.error("SSL证书文件缺失，请确保cert.pem和key.pem文件存在");
    process.exit(1);
  }
} else {
  // Development: HTTP without SSL
  server = http.createServer(app); // 将Express应用作为HTTP服务器
  wss = new WebSocket.Server({
    server, // 使用同一个HTTP服务器
    verifyClient: info =>
      info.origin &&
      !!info.origin.match(
        /^https?:\/\/([^.]+\.github\.io|localhost|clocktower\.online|eddbra1nprivatetownsquare\.xyz)/i
      )
  });
}

function noop() {}

// calculate latency on heartbeat
function heartbeat() {
  this.latency = Math.round((new Date().getTime() - this.pingStart) / 2);
  this.counter = 0;
  this.isAlive = true;
}

// map of channels currently in use
const channels = {};

// 全局用户管理器
const globalUserManager = new GlobalUserManager();

// 全局房间管理器
const globalRoomManager = new GlobalRoomManager();

// 兼容性：旧的房间数据结构（逐步迁移到新的房间管理器）
const voiceRooms = new Map();
const publicSets = new Map();

// 兼容性函数：获取公聊参与者集合
function getPublicSet(channel) {
  const roomManager = globalRoomManager.getChannelManager(channel);
  const publicRoom = roomManager.getPublicRoom();
  return new Set(publicRoom.getParticipants());
}

// 新的房间管理函数
function getChannelRoomManager(channel) {
  return globalRoomManager.getChannelManager(channel);
}

function createPrivateRoom(channel, ownerId) {
  const roomManager = getChannelRoomManager(channel);
  return roomManager.createPrivateRoom(ownerId);
}

function getPrivateRoom(channel, roomId) {
  const roomManager = getChannelRoomManager(channel);
  return roomManager.getPrivateRoom(roomId);
}

// Per-channel revision for voice state updates
const channelRevision = new Map();
function nextRevision(channel) {
  const cur = (channelRevision.get(channel) || 0) + 1;
  channelRevision.set(channel, cur);
  return cur;
}
function currentRevision(channel) {
  return channelRevision.get(channel) || 0;
}

// 兼容性函数：从用户管理器获取角色信息
function getChannelRoles(channel) {
  const channelManager = globalUserManager.getChannelManager(channel);
  const users = channelManager.getOnlineUsers();
  const roles = new Map();
  users.forEach(user => {
    roles.set(user.playerId, user.role);
  });
  return roles;
}

function setPlayerRole(channel, playerId, role) {
  const channelManager = globalUserManager.getChannelManager(channel);
  const user = channelManager.getUser(playerId);
  if (user && user.role !== role) {
    user.updateRole(role);
    // broadcast role change
    broadcastToChannel(channel, 'voice_role_update', { playerId, role }, null);
    // broadcast user update
    broadcastUserUpdate(channel, user);
  }
}

function getChannelRooms(channel) {
  if (!voiceRooms.has(channel)) {
    voiceRooms.set(channel, new Map());
  }
  return voiceRooms.get(channel);
}

function broadcastToChannel(channel, command, payload, excludeWs) {
  (channels[channel] || []).forEach(function each(client) {
    if (client.readyState === WebSocket.OPEN && client !== excludeWs) {
      client.send(JSON.stringify([command, payload]));
      metrics.messages_outgoing.inc();
    }
  });
}

// 广播用户更新
function broadcastUserUpdate(channel, user) {
  broadcastToChannel(channel, 'user_update', user.getSummary(), null);
}

// 广播用户列表
function broadcastUserList(channel) {
  const channelManager = globalUserManager.getChannelManager(channel);
  const usersSummary = channelManager.getUsersSummary();
  broadcastToChannel(channel, 'users_snapshot', usersSummary, null);
}

// 广播房间更新
function broadcastRoomUpdate(channel, room) {
  broadcastToChannel(channel, 'room_update', room.getSummary(), null);
}

// 广播房间列表
function broadcastRoomList(channel) {
  const roomManager = globalRoomManager.getChannelManager(channel);
  const roomsSummary = roomManager.getRoomsSummary();
  broadcastToChannel(channel, 'rooms_snapshot', roomsSummary, null);
}

// 发送给特定用户
function directToPlayer(channel, playerId, command, payload) {
  let delivered = false;
  (channels[channel] || []).forEach(function each(client) {
    if (client.readyState === WebSocket.OPEN && client.playerId === playerId) {
      client.send(JSON.stringify([command, payload]));
      metrics.messages_outgoing.inc();
      delivered = true;
    }
  });
  // Fallback: if target未找到且频道内存在host，尝试发给host（房主可能是说书人）
  if (!delivered) {
    (channels[channel] || []).forEach(function each(client) {
      if (client.readyState === WebSocket.OPEN && client.playerId === 'host') {
        client.send(JSON.stringify([command, payload]));
        metrics.messages_outgoing.inc();
        delivered = true;
      }
    });
  }
  return delivered;
}

function normalizeTargetId(id) {
  if (!id) return id;
  return id === 'storyteller' ? 'host' : id;
}

function isHost(ws) {
  return ws && ws.playerId === 'host';
}

function isRoomOwner(ws, room) {
  return ws && room && ws.playerId === room.ownerId;
}

function parseWs(data) {
  try {
    return JSON.parse(data);
  } catch (e) {
    console.log('error parsing JSON', e);
    return null;
  }
}

function broadcastFiltered(channel, command, payload, predicate, excludeWs) {
  (channels[channel] || []).forEach(function each(client) {
    if (
      client.readyState === WebSocket.OPEN &&
      client !== excludeWs &&
      (!predicate || predicate(client))
    ) {
      client.send(JSON.stringify([command, payload]));
      metrics.messages_outgoing.inc();
    }
  });
}

function broadcastRaw(channel, raw, excludeWs) {
  (channels[channel] || []).forEach(function each(client) {
    if (client !== excludeWs && client.readyState === WebSocket.OPEN) {
      client.send(raw);
      metrics.messages_outgoing.inc();
    }
  });
}

// metrics
const metrics = {
  players_concurrent: new client.Gauge({
    name: "players_concurrent",
    help: "Concurrent Players",
    collect() {
      this.set(wss.clients.size);
    }
  }),
  channels_concurrent: new client.Gauge({
    name: "channels_concurrent",
    help: "Concurrent Channels",
    collect() {
      this.set(Object.keys(channels).length);
    }
  }),
  channels_list: new client.Gauge({
    name: "channel_players",
    help: "Players in each channel",
    labelNames: ["name"],
    collect() {
      for (let channel in channels) {
        this.set(
          { name: channel },
          channels[channel].filter(
            ws =>
              ws &&
              (ws.readyState === WebSocket.OPEN ||
                ws.readyState === WebSocket.CONNECTING)
          ).length
        );
      }
    }
  }),
  messages_incoming: new client.Counter({
    name: "messages_incoming",
    help: "Incoming messages"
  }),
  messages_outgoing: new client.Counter({
    name: "messages_outgoing",
    help: "Outgoing messages"
  }),
  connection_terminated_host: new client.Counter({
    name: "connection_terminated_host",
    help: "Terminated connection due to host already present"
  }),
  connection_terminated_spam: new client.Counter({
    name: "connection_terminated_spam",
    help: "Terminated connection due to message spam"
  }),
  connection_terminated_timeout: new client.Counter({
    name: "connection_terminated_timeout",
    help: "Terminated connection due to timeout"
  })
};

// register metrics
for (let metric in metrics) {
  register.registerMetric(metrics[metric]);
}

// a new client connects
wss.on("connection", function connection(ws, req) {
  // url pattern: clocktower.online/<channel>/<playerId|host>
  const url = req.url.toLocaleLowerCase().split("/");
  ws.playerId = url.pop();
  ws.channel = url.pop();
  // 使用新的用户管理系统初始化用户
  try {
    const channelManager = globalUserManager.getChannelManager(ws.channel);
    let role = USER_ROLES.SPECTATOR;
    
    if (ws.playerId === 'host') {
      role = USER_ROLES.STORYTELLER;
    }
    
    const user = channelManager.addUser(ws.playerId, role, ws.id || Date.now());
    console.log(`用户 ${ws.playerId} 加入频道 ${ws.channel}，角色：${role}`);
  } catch (e) { 
    console.error('初始化用户失败:', e);
  }
  // check for another host on this channel
  if (
    ws.playerId === "host" &&
    channels[ws.channel] &&
    channels[ws.channel].some(
      client =>
        client !== ws &&
        client.readyState === WebSocket.OPEN &&
        client.playerId === "host"
    )
  ) {
    console.log(ws.channel, "duplicate host");
    ws.close(1000, `The channel "${ws.channel}" already has a host`);
    metrics.connection_terminated_host.inc();
    return;
  }
  ws.isAlive = true;
  ws.pingStart = new Date().getTime();
  ws.counter = 0;
  // add channel to list
  if (!channels[ws.channel]) {
    channels[ws.channel] = [];
  }
  channels[ws.channel].push(ws);
  // start ping pong
  ws.ping(noop);
  ws.on("pong", heartbeat);
  // 发送当前频道的完整状态快照给新连接的客户端
  try {
    const channelManager = globalUserManager.getChannelManager(ws.channel);
    const roomManager = globalRoomManager.getChannelManager(ws.channel);
    
    // 兼容性：构建旧格式的语音状态数据
    const rolesMap = getChannelRoles(ws.channel);
    const rolesObj = {}; rolesMap.forEach((r, pid) => rolesObj[pid] = r);
    
    const publicRoom = roomManager.getPublicRoom();
    const privateRooms = roomManager.getActivePrivateRooms();
    const privateChats = {};
    
    privateRooms.forEach((room) => {
      privateChats[room.roomId] = {
        active: room.status === ROOM_STATUS.ACTIVE,
        ownerId: room.ownerId,
        participants: room.getParticipants(),
        createdAt: room.createdAt
      };
    });
    
    // 发送语音状态快照（兼容性）
    directToPlayer(ws.channel, ws.playerId, 'voice_state_snapshot', {
      revision: currentRevision(ws.channel),
      roles: rolesObj,
      publicChat: { 
        participants: publicRoom.getParticipants(), 
        active: publicRoom.status === ROOM_STATUS.ACTIVE 
      },
      privateChats
    });
    
    // 发送用户管理数据快照
    directToPlayer(ws.channel, ws.playerId, 'users_snapshot', channelManager.getUsersSummary());
    
    // 发送房间管理数据快照
    directToPlayer(ws.channel, ws.playerId, 'rooms_snapshot', roomManager.getRoomsSummary());
    
    // 广播新用户加入给其他用户
    const user = channelManager.getUser(ws.playerId);
    if (user) {
      broadcastToChannel(ws.channel, 'user_joined', user.getSummary(), ws);
    }
  } catch (e) { 
    console.error('发送状态快照失败:', e);
  }
  // handle message
  ws.on("message", function incoming(data) {
    metrics.messages_incoming.inc();
    // check rate limit (max 5msg/second)
    ws.counter++;
    if (ws.counter > (5 * PING_INTERVAL) / 1000) {
      console.log(ws.channel, "disconnecting user due to spam");
      ws.close(
        1000,
        "Your app seems to be malfunctioning, please clear your browser cache."
      );
      metrics.connection_terminated_spam.inc();
      return;
    }
    const messageType = data
      .toLocaleLowerCase()
      .substr(1)
      .split(",", 1)
      .pop();
    switch (messageType) {
      case '"ping"':
        // ping messages will only be sent host -> all or all -> host
        broadcastFiltered(ws.channel, null, null, (client) => (
          client !== ws && (ws.playerId === 'host' || client.playerId === 'host')
        ));
        break;
      case '"claim"': {
        // 座位认领：根据是否入座更新角色 guest -> player / player -> guest
        try {
          const parsed = parseWs(data); if (!parsed) break; const [command, payload] = parsed;
          // payload 形如 [seatIndex, playerId]
          const seatIndex = Array.isArray(payload) ? payload[0] : null;
          const playerId = Array.isArray(payload) ? payload[1] : null;
          if (typeof seatIndex !== 'number' || !playerId) break;
          // seatIndex >= 0 视为入座
          if (seatIndex >= 0) {
            setPlayerRole(ws.channel, playerId, 'player');
          } else {
            setPlayerRole(ws.channel, playerId, 'guest');
          }
        } catch (e) {
          console.log('error handling claim for role update', e);
        }
        // 仍按原逻辑把消息广播给 host / players
        broadcastRaw(ws.channel, data, ws);
        break; }
      case '"direct"':
        // handle "direct" messages differently
        console.log(
          new Date(),
          wss.clients.size,
          ws.channel,
          ws.playerId,
          data
        );
        try {
          const parsed = parseWs(data);
          if (!parsed) break;
          const dataToPlayer = parsed[1];
          (channels[ws.channel] || []).forEach(function each(client) {
            if (
              client !== ws &&
              client.readyState === WebSocket.OPEN &&
              dataToPlayer[client.playerId]
            ) {
              client.send(JSON.stringify(dataToPlayer[client.playerId]));
              metrics.messages_outgoing.inc();
            }
          });
        } catch (e) {
          console.log("error parsing direct message JSON", e);
        }
        break;
      case '"private_message"':
        // 处理私聊消息
        try {
          const parsed = parseWs(data); if (!parsed) break; const [command, messageData] = parsed;
          console.log(
            new Date(),
            "Private message:",
            ws.channel,
            ws.playerId,
            "->",
            messageData.receiverId
          );
          
          // 验证消息格式
          if (!messageData.senderId || !messageData.receiverId || !messageData.content) {
            console.warn("Invalid private message format:", messageData);
            break;
          }
          
          // 权限验证：只有说书人可以发私聊，或玩家只能回复给说书人
          const isStoryteller = ws.playerId === "host";
          const isPlayerReplyingToStoryteller = messageData.receiverId === "storyteller";
          
          console.log("🔐 权限验证:", {
            senderId: ws.playerId,
            isStoryteller,
            receiverId: messageData.receiverId,
            isPlayerReplyingToStoryteller
          });
          
          if (!isStoryteller && !isPlayerReplyingToStoryteller) {
            console.warn("❌ Unauthorized private message attempt:", ws.playerId, "->", messageData.receiverId);
            break;
          }
          
          console.log("✅ 权限验证通过");
          
          // 转发消息给目标接收者
          const targetPlayerId = normalizeTargetId(messageData.receiverId);
          
          console.log("🎯 查找目标接收者:", {
            receiverId: messageData.receiverId,
            targetPlayerId,
            channelClients: channels[ws.channel]?.length || 0
          });
          
          let messageForwarded = false;
          (channels[ws.channel] || []).forEach(function each(client) {
            if (
              client !== ws &&
              client.readyState === WebSocket.OPEN &&
              client.playerId === targetPlayerId
            ) {
              client.send(JSON.stringify([command, messageData]));
              metrics.messages_outgoing.inc();
              messageForwarded = true;
            }
          });
          
          if (!messageForwarded) {
            console.warn("❌ 目标接收者未找到或不在线:", targetPlayerId);
          }
          
        } catch (e) {
          console.log("error parsing private message JSON", e);
        }
        break;
      case '"private_message_delivered"':
        // 处理消息送达确认
        try {
          const parsed = parseWs(data); if (!parsed) break; const [command, deliveryData] = parsed;
          console.log(
            new Date(),
            "Message delivery confirmation:",
            ws.channel,
            deliveryData.messageId
          );
          
          // 转发送达确认给发送方
          const senderPlayerId = normalizeTargetId(deliveryData.senderId);
          
          directToPlayer(ws.channel, senderPlayerId, command, deliveryData);
          
        } catch (e) {
          console.log("error parsing message delivery confirmation JSON", e);
        }
        break;
      case '"voice_chat_request"':
        // 处理语音聊天申请
        try {
          const parsed = parseWs(data); if (!parsed) break; const [command, requestData] = parsed;
          console.log(
            new Date(),
            "Voice chat request:",
            ws.channel,
            ws.playerId,
            "->",
            requestData.toId
          );
          
          // 验证申请格式
          if (!requestData.fromId || !requestData.toId || !requestData.type) {
            console.warn("Invalid voice chat request format:", requestData);
            break;
          }
          
          // 转发申请给目标用户
          const targetPlayerId = normalizeTargetId(requestData.toId);
          let requestForwarded = false;
          
          requestForwarded = directToPlayer(ws.channel, targetPlayerId, command, requestData);
          
          if (!requestForwarded) {
            console.warn("❌ 目标用户未找到或不在线:", targetPlayerId);
          }
          
        } catch (e) {
          console.log("error parsing voice chat request JSON", e);
        }
        break;
      case '"voice_chat_join"': {
        // 由服务端裁决加入公聊并广播权威列表
        try {
          const parsed = parseWs(data); if (!parsed) break; const [command, payload] = parsed;
          const playerId = payload && payload.playerId;
          if (!playerId) break;
          const roles = getChannelRoles(ws.channel);
          const normalizedId = (playerId === 'host' || roles.get(playerId) === 'storyteller' || ws.playerId === 'host') ? 'host' : playerId;
          const pub = getPublicSet(ws.channel);
          pub.add(normalizedId);
          // 不在此处移除私聊成员，由私聊创建/加入时已维护互斥
          const participants = Array.from(pub);
          broadcastToChannel(ws.channel, 'voice_public_update', {
            revision: nextRevision(ws.channel),
            participants,
            active: participants.length > 0,
            // 可选携带本次加入者的角色提示
            joined: { playerId: normalizedId, role: roles.get(normalizedId) || 'guest' }
          }, null);
        } catch (e) {
          console.log('error handling voice_chat_join', e);
        }
        break; }
      case '"voice_chat_leave"': {
        // 裁决离开公聊并广播权威列表
        try {
          const parsed = parseWs(data); if (!parsed) break; const [command, payload] = parsed;
          const playerId = payload && payload.playerId;
          if (!playerId) break;
          const pub = getPublicSet(ws.channel);
          pub.delete(playerId);
          const participants = Array.from(pub);
          broadcastToChannel(ws.channel, 'voice_public_update', {
            revision: nextRevision(ws.channel),
            participants,
            active: participants.length > 0,
            left: { playerId }
          }, null);
        } catch (e) {
          console.log('error handling voice_chat_leave', e);
        }
        break; }
      case '"voice_private_create_room"': {
        // 房主创建私聊房间并邀请目标
        try {
          const parsed = parseWs(data); if (!parsed) break; const [command, payload] = parsed;
          const { roomId, ownerId, targetIds = [] } = payload || {};
          if (!roomId || !ownerId) break;
          // 校验：只有owner本人或host可创建
          const isHost = ws.playerId === 'host';
          if (!isHost && ws.playerId !== ownerId) break;
          const rooms = getChannelRooms(ws.channel);
          const roles = getChannelRoles(ws.channel);
          const ownerIdNorm = (ownerId === 'host' || roles.get(ownerId) === 'storyteller' || isHost) ? 'host' : ownerId;
          if (!rooms.has(roomId)) {
            rooms.set(roomId, {
              ownerId: ownerIdNorm,
              participants: new Set([ownerIdNorm]),
              active: true,
              pendingInvites: new Set(targetIds),
              joinPolicy: 'ownerApproval',
              createdAt: Date.now()
            });
          }
          // 从公聊中剔除房间参与者（owner）
          const pub = getPublicSet(ws.channel);
          pub.delete(ownerIdNorm);
          nextRevision(ws.channel);
          // 广播房间创建给所有客户端
          broadcastToChannel(ws.channel, 'voice_private_room_update', {
            action: 'created',
            roomId,
            ownerId: ownerIdNorm,
            participants: Array.from(rooms.get(roomId).participants),
            createdAt: rooms.get(roomId).createdAt
          }, null);
          // 直发邀请给目标玩家
          // 尝试从角色/显示名推断更友好的房主名称
          const ownerName = (roles.get(ownerIdNorm) === 'storyteller') ? 'storyteller' : ownerIdNorm;
          targetIds.forEach(targetId => {
            directToPlayer(ws.channel, targetId, 'voice_private_invite', {
              roomId,
              ownerId: ownerIdNorm,
              ownerName,
              snapshot: {
                ownerId: ownerIdNorm,
                participants: [ownerIdNorm],
                createdAt: rooms.get(roomId).createdAt
              }
            });
          });
        } catch (e) {
          console.log('error handling voice_private_create_room', e);
        }
        break; }
      case '"voice_private_invite_response"': {
        // 被邀请者响应
        try {
          const parsed = parseWs(data); if (!parsed) break; const [command, payload] = parsed;
          const { roomId, inviteeId, approved } = payload || {};
          if (!roomId || !inviteeId) break;
          const rooms = getChannelRooms(ws.channel);
          const room = rooms.get(roomId);
          if (!room || !room.active) break;
          if (approved) {
            room.participants.add(inviteeId);
            // 该参与者从公聊剔除
            getPublicSet(ws.channel).delete(inviteeId);
            nextRevision(ws.channel);
            // 广播增量加入
            broadcastToChannel(ws.channel, 'voice_private_room_update', {
              action: 'participant_added',
              roomId,
              ownerId: room.ownerId,
              participantId: inviteeId,
              participants: Array.from(room.participants)
            }, null);
          }
          // 直发回执给被邀请者（用于提示）
          directToPlayer(ws.channel, inviteeId, 'voice_private_join_response', {
            roomId,
            requesterId: inviteeId,
            approved
          });
        } catch (e) {
          console.log('error handling voice_private_invite_response', e);
        }
        break; }
      case '"voice_private_join_request"': {
        // 申请加入房间（非受邀）
        try {
          const parsed = parseWs(data); if (!parsed) break; const [command, payload] = parsed;
          const { roomId, requesterId } = payload || {};
          if (!roomId || !requesterId) break;
          const rooms = getChannelRooms(ws.channel);
          const room = rooms.get(roomId);
          if (!room || !room.active) break;
          // 转发到房主以审批
          directToPlayer(ws.channel, room.ownerId, 'voice_private_join_request', {
            roomId,
            requesterId
          });
        } catch (e) {
          console.log('error handling voice_private_join_request', e);
        }
        break; }
      case '"voice_private_join_response"': {
        // 房主审批加入
        try {
          const parsed = parseWs(data); if (!parsed) break; const [command, payload] = parsed;
          const { roomId, requesterId, approved } = payload || {};
          if (!roomId || !requesterId) break;
          const rooms = getChannelRooms(ws.channel);
          const room = rooms.get(roomId);
          if (!room || !room.active) break;
          // 只有房主或host可审批
          if (ws.playerId !== room.ownerId && ws.playerId !== 'host') break;
          if (approved) {
            room.participants.add(requesterId);
            broadcastToChannel(ws.channel, 'voice_private_room_update', {
              action: 'participant_added',
              roomId,
              ownerId: room.ownerId,
              participantId: requesterId,
              participants: Array.from(room.participants)
            }, null);
          }
          // 直发结果给申请者
          directToPlayer(ws.channel, requesterId, 'voice_private_join_response', {
            roomId,
            requesterId,
            approved
          });
        } catch (e) {
          console.log('error handling voice_private_join_response', e);
        }
        break; }
      case '"voice_private_leave"': {
        // 成员离开（或前端显式触发）
        try {
          const [command, payload] = JSON.parse(data);
          const { roomId, playerId } = payload || {};
          if (!roomId || !playerId) break;
          const rooms = getChannelRooms(ws.channel);
          const room = rooms.get(roomId);
          if (!room || !room.active) break;
          const beforeSize = room.participants.size;
          room.participants.delete(playerId);
          if (room.participants.size === 0) {
            room.active = false;
            broadcastToChannel(ws.channel, 'voice_private_room_update', {
              action: 'closed',
              roomId
            }, null);
            nextRevision(ws.channel);
          } else {
            const remaining = Array.from(room.participants);
            if (playerId === room.ownerId) {
              // 房主离开，转移所有权（确定性）
              const newOwnerId = remaining.sort()[0];
              room.ownerId = newOwnerId;
              broadcastToChannel(ws.channel, 'voice_private_room_update', {
                action: 'owner_changed',
                roomId,
                ownerId: newOwnerId,
                participants: remaining
              }, null);
              // 可选：直发提示给新房主
              directToPlayer(ws.channel, newOwnerId, 'voice_chat_notice', {
                type: 'owner_changed',
                roomId,
                message: '你已经成为房主'
              });
              // 房主（离开者）自动回到公聊，方便玩家看到说书人
              broadcastToChannel(ws.channel, 'voice_chat_join', {
                type: 'public',
                playerId: playerId,
                playerName: playerId,
                isSeated: false,
                isStoryteller: true,
                displayName: '【⭐】说书人',
                action: 'join'
              }, null);
              getPublicSet(ws.channel).add(playerId);
              nextRevision(ws.channel);
            } else {
              broadcastToChannel(ws.channel, 'voice_private_room_update', {
                action: 'participant_removed',
                roomId,
                removedPlayerId: playerId,
                remainingParticipants: remaining
              }, null);
              // 离开者若选择回公聊，应另行发送voice_chat_join，这里不默认加入
              nextRevision(ws.channel);
            }
          }
        } catch (e) {
          console.log('error handling voice_private_leave', e);
        }
        break; }
      case '"voice_chat_response"':
        // 处理语音聊天申请回复
        try {
          const parsed = parseWs(data); if (!parsed) break; const [command, responseData] = parsed;
          console.log(
            new Date(),
            "Voice chat response:",
            ws.channel,
            responseData.approved ? "APPROVED" : "REJECTED"
          );
          
          // 转发回复给申请方
          const requesterPlayerId = responseData.fromId === "storyteller" ? "host" : responseData.fromId;
          
          directToPlayer(ws.channel, requesterPlayerId, command, responseData);
          
        } catch (e) {
          console.log("error parsing voice chat response JSON", e);
        }
        break;
      case '"voice_chat_control"':
        // 处理说书人语音控制命令
        try {
          const parsed = parseWs(data); if (!parsed) break; const [command, controlData] = parsed;
          console.log(
            new Date(),
            "Voice chat control:",
            ws.channel,
            ws.playerId,
            controlData.type
          );
          
          // 只有说书人可以发送控制命令
          if (ws.playerId !== "host") {
            console.warn("❌ Non-host trying to send voice control:", ws.playerId);
            break;
          }
          
          // 广播控制命令给所有玩家
          broadcastFiltered(ws.channel, command, controlData, (client) => client !== ws && client.playerId !== 'host');
          
        } catch (e) {
          console.log("error parsing voice chat control JSON", e);
        }
        break;
      case '"webrtc_signal"':
        // 处理WebRTC信令
        try {
          const parsed = parseWs(data); if (!parsed) break; const [command, signalData] = parsed;
          console.log(
            new Date(),
            "WebRTC signal:",
            ws.channel,
            ws.playerId,
            "->",
            signalData.targetId,
            signalData.type
          );
          
          // 转发WebRTC信令给目标用户
          const targetPlayerId = normalizeTargetId(signalData.targetId);
          
          (channels[ws.channel] || []).forEach(function each(client) {
            if (
              client !== ws &&
              client.readyState === WebSocket.OPEN &&
              client.playerId === targetPlayerId
            ) {
              client.send(JSON.stringify([command, {
                ...signalData,
                fromId: isHost(ws) ? 'storyteller' : ws.playerId
              }]));
              metrics.messages_outgoing.inc();
            }
          });
          
        } catch (e) {
          console.log("error parsing WebRTC signal JSON", e);
        }
        break;
      default:
        // all other messages
        console.log(
          new Date(),
          wss.clients.size,
          ws.channel,
          ws.playerId,
          data
        );
        broadcastRaw(ws.channel, data, ws);
        break;
    }
  });
});

// start ping interval timer
const interval = setInterval(function ping() {
  // ping each client
  wss.clients.forEach(function each(ws) {
    if (ws.isAlive === false) {
      metrics.connection_terminated_timeout.inc();
      // 清理用户数据
      if (ws.channel && ws.playerId) {
        try {
          const channelManager = globalUserManager.getChannelManager(ws.channel);
          const user = channelManager.removeUser(ws.playerId);
          if (user) {
            console.log(`用户 ${ws.playerId} 因超时断开连接，已清理`);
            // 广播用户离开
            broadcastToChannel(ws.channel, 'user_left', user.getSummary(), null);
          }
        } catch (e) {
          console.error('清理超时用户失败:', e);
        }
      }
      return ws.terminate();
    }
    ws.isAlive = false;
    ws.pingStart = new Date().getTime();
    ws.ping(noop);
  });
  // clean up empty channels
  for (let channel in channels) {
    if (
      !channels[channel].length ||
      !channels[channel].some(
        ws =>
          ws &&
          (ws.readyState === WebSocket.OPEN ||
            ws.readyState === WebSocket.CONNECTING)
      )
    ) {
      metrics.channels_list.remove({ name: channel });
      // 清理频道的用户数据
      try {
        globalUserManager.removeChannel(channel);
        console.log(`频道 ${channel} 已清空并清理用户数据`);
      } catch (e) {
        console.error('清理频道用户数据失败:', e);
      }
      delete channels[channel];
    }
  }
}, PING_INTERVAL);

// handle server shutdown
wss.on("close", function close() {
  clearInterval(interval);
});

// 统一服务器启动
const PORT = config.SERVER_PORT;

server.listen(PORT, () => {
  config.logger.info(`🚀 服务器启动成功！`);
  config.logger.info(`📱 客户端地址: ${config.CLIENT_URL}`);
  config.logger.info(`🔌 WebSocket服务器: ws://localhost:${PORT}`);
  config.logger.info(`🌐 API服务器: http://localhost:${PORT}/api`);
  config.logger.info(`📊 监控指标: http://localhost:${PORT}/metrics`);
  
  if (process.env.NODE_ENV === "production") {
    // 生产模式服务器启动
    console.log(`🏭 运行模式: 生产环境`);
    
    // 添加监控指标API
    server.on("request", (req, res) => {
      if (req.url === '/metrics') {
        res.setHeader("Content-Type", register.contentType);
        register.metrics().then(out => res.end(out));
      }
    });
  } else {
    // 开发模式服务器启动
    console.log(`🔧 运行模式: 开发环境`);
  }
});
